package com.crazymaker.springcloud.base.security.provider;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.crazymaker.springcloud.base.security.token.JwtAuthenticationToken;
import com.crazymaker.springcloud.common.constants.SessionConstants;
import com.crazymaker.springcloud.common.context.SessionHolder;
import com.crazymaker.springcloud.common.dto.UserDTO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.www.NonceExpiredException;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.util.StringUtils;

import java.util.Calendar;

public class JwtAuthenticationProvider implements AuthenticationProvider {

    private RedisOperationsSessionRepository sessionRepository;

    public JwtAuthenticationProvider(RedisOperationsSessionRepository sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) authentication;

        // 获取jwt
        DecodedJWT jwt = jwtToken.getDecodedJWT();
        // 判断是否过期
        if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) {
            throw new NonceExpiredException("认证过期");
        }
        // session id
        String sessionId = jwt.getSubject();
        // 用来判断是否登录登录
        String newToken = jwt.getToken();
        // 判断session是否存在
        Session session = null;
        try {
            session = sessionRepository.findById(sessionId);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (session == null) {
            throw new NonceExpiredException("还没有登录，请登录系统");
        }

        String json = session.getAttribute(SessionHolder.G_USER);
        if (StringUtils.isEmpty(json)) {
            throw new NonceExpiredException("认证有误，请重新登录");
        }

        UserDTO userDTO = JSON.parseObject(json, UserDTO.class);

        if (null == userDTO) {
            throw new NonceExpiredException("认证有误，请重新登录");
        }

        if (null == newToken || !newToken.equals(userDTO.getToken())) {
            throw new NonceExpiredException("您已经在其他地方登录！");
        }

        String userId = null;
        if (null == userDTO.getUserId()) {
            userId = String.valueOf(userDTO.getId());
        }
        else {
            userId = String.valueOf(userDTO.getUserId());
        }

        UserDetails userDetails = User.builder()
                .username(userId)
                .password(userDTO.getPassword())
                .authorities(SessionConstants.USER_INFO)
                .build();

        // 对token进行安全性校验
        try {
            String entrySalt = userDTO.getPassword();
            Algorithm algorithm = Algorithm.HMAC256(entrySalt);

            JWTVerifier verifier = JWT.require(algorithm).withSubject(sessionId).build();
            verifier.verify(newToken);
        } catch (Exception e) {
            throw new BadCredentialsException("认证有误：令牌校验失败，请重新登录", e);
        }

        // 返回一个新的校验通过的token
        JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(userDetails.getAuthorities(), jwt, userDetails);
        authenticationToken.setAuthenticated(true);
        return authenticationToken;
    }

    /**
     * 仅支持jwt token
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(JwtAuthenticationToken.class);
    }
}
